Skip to content

Fix IPv6 address with zone ID SSRF bypass - #445

Merged
marksmith merged 1 commit into
mainfrom
fix-ipv6-address-with-zone-id-ssrf-bypass
Oct 9, 2026
Merged

marksmith merged 1 commit into
mainfrom
fix-ipv6-address-with-zone-id-ssrf-bypass

Conversation

@marksmith

Copy link
Copy Markdown
Collaborator

This change fixes an SSRF bypass; a private IPv6 address with a zone ID (i.e. fe80::1%eth0) is not recognized as a private IP address and is treated as a hostname by Aikido::Zen::Scanners::SSRF::PrivateIPChecker. The private IP checker now recognizes IPv6 addresses with an optional zone ID as IP addresses, and removes the zone ID before building an IPAddr (IPAddr.new before Ruby 3.1 raises when given a zone ID). The zone ID does not affect which range an IP address is in.

@codecov

codecov Bot commented Oct 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment on lines 108 to 117
def parse_address(address)
return nil unless address.is_a?(String) && ADDRESS_REGEXP.match?(address)

Socket.getaddrinfo(address, nil, :UNSPEC, :STREAM, nil, Socket::AI_NUMERICHOST)
.map { |info| IPAddr.new(info[3]) }
Socket.getaddrinfo(address, nil, :UNSPEC, :STREAM, nil, Socket::AI_NUMERICHOST).map do |info|
# `IPAddr.new` only accepts a zone ID on Ruby 3.1+.
IPAddr.new(info[3].partition("%").first)
end
rescue SocketError
nil
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High - RFC 6874 encoded zone IDs still bypass private-IP detection

A caller sends a valid RFC 6874 URL such as http://[fe80::1%25lo] through a supported HTTP sink. The URI hostname retains the encoded %25, but parse_address forwards it unchanged to getaddrinfo; when that scope lookup fails, resolution falls through without checking the underlying IPv6 address. The scanner therefore treats a link-local/private IPv6 target as external and permits an SSRF when the HTTP client accepts and connects the encoded URL.

Show fix
Suggested change
def parse_address(address)
return nil unless address.is_a?(String) && ADDRESS_REGEXP.match?(address)
Socket.getaddrinfo(address, nil, :UNSPEC, :STREAM, nil, Socket::AI_NUMERICHOST)
.map { |info| IPAddr.new(info[3]) }
Socket.getaddrinfo(address, nil, :UNSPEC, :STREAM, nil, Socket::AI_NUMERICHOST).map do |info|
# `IPAddr.new` only accepts a zone ID on Ruby 3.1+.
IPAddr.new(info[3].partition("%").first)
end
rescue SocketError
nil
end
def parse_address(address)
return nil unless address.is_a?(String) && ADDRESS_REGEXP.match?(address)
address = address.sub(/%25/i, "%")
Socket.getaddrinfo(address, nil, :UNSPEC, :STREAM, nil, Socket::AI_NUMERICHOST).map do |info|
# `IPAddr.new` only accepts a zone ID on Ruby 3.1+.
IPAddr.new(info[3].partition("%").first)
end
rescue SocketError
nil
end

More info - Reply on this comment to give feedback or ignore the issue.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The builtin URI class does not implement RFC 6874, and raises URI::InvalidURIError when given a URI containing an IPv6 address with a zone ID to parse. How best to accommodate other URI implementations is currently being considered.

@marksmith
marksmith merged commit f3ba116 into main Oct 9, 2026
41 checks passed
@marksmith
marksmith deleted the fix-ipv6-address-with-zone-id-ssrf-bypass branch October 9, 2026 09:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants